#include "stdafx.h"

#include "Util.h"
#include "Entity.h"
#include "Database.h"
using namespace System::Text;
using namespace System::IO;
using namespace System::Collections::Generic;
using namespace System::Runtime::InteropServices; // for class Marshal
using namespace System::Diagnostics;

namespace ManagedAPI
{
	Entity^ Entity::Create(const UdbEntity& entity)
	{
		Debug::Assert(entity != nullptr);
		char* keystr = udbEntityNameUnique(entity);
		String^ key = gcnew String(keystr);

		Entity^ result = nullptr;
		if (sEntList->ContainsKey(key))
		{
			result = sEntList[key];
			Debug::Assert(result != nullptr);
		}
		else
		{
			result = gcnew Entity(entity);
			sEntList->Add(key, result);
		}
		return result;
	}
	void Entity::Clear()
	{
		sEntList->Clear();
	}

	Entity::Entity()
	{
		// This shouldn't ever be called.
		Debug::Assert(false);
	}
	Entity::Entity(const UdbEntity& entity)
#ifdef CACHING_POINTERS_BUG
		: mEntity(entity),
		mType(nullptr),
		mLineDefineStart(0),
		mLineDefineEnd(0)
#else
		: mType(nullptr)
#endif
	{		
		char* entNameShort = udbEntityNameShort(entity);
		mNameShort = gcnew String(entNameShort);

		char* entNameSimple = udbEntityNameSimple(entity);
		mNameSimple = gcnew String(entNameSimple);
#ifdef TESTING
		if (String::Compare("MakeMemory", mNameSimple) == 0)
		{
			int i = 6;
			i++;
		}
#endif
		char* entNameLong = udbEntityNameLong(entity);
		mNameLong = gcnew String(entNameLong);

		mId = udbEntityId(entity);

		::UdbKind kindID = udbEntityKind(entity);
		mThisKind = gcnew ManagedAPI::Kind( kindID);
			
		char* entType = udbEntityTypetext(entity);
		if (entType != 0)
		{
			mType = gcnew String(entType);
		}
	
		char* tempUniqueNameChar = udbEntityNameUnique(entity);
		mUniqueName = gcnew String(tempUniqueNameChar);

		// Initialize the comments
		mComments = gcnew ManagedAPI::Comments(entity);

		InitializeBoundsaries(entity);
	}

	Entity::~Entity()
	{
		
	}

	String^ Entity::NameShort::get()
	{
		return mNameShort;
	}
	String^ Entity::NameSimple::get()
	{
		return mNameSimple;
	}
	String^ Entity::NameLong::get()
	{
		return mNameLong;
	}
	String^ Entity::NameUnique::get()
	{
		return mUniqueName;
	}

	int     Entity::ID::get()
	{
		return mId;
	}
	ManagedAPI::Kind^ Entity::Kind::get()
	{
		return mThisKind;
	}
	String^ Entity::KindName::get()
	{
		return mThisKind->NameLong;
	}
	String^ Entity::TypeText::get()
	{
		return mType;
	}
	int Entity::LineDefineStart::get()
	{
		return mLineDefineStart;
	}
	int Entity::LineDefineEnd::get()
	{
		return mLineDefineEnd;
	}
	int Entity::LineDeclareStart::get()
	{
		return mLineDeclareStart;
	}
	ManagedAPI::Comments^ Entity::Comments::get()
	{
		return mComments;
	}
	String^ Entity::GetCleanType()
	{
		if ( ! String::IsNullOrEmpty(mType) )
		{
			return Util::CleanUpType(mType);
		}
		return nullptr;
	}
	Entity^ Entity::GetTypeEntity()
	{
		String^ type = GetCleanType();
		char* charType = Util::StringToChar(type);

		UdbEntity* pEnts = 0;
		int match = 1;
		udbLookupEntity( charType, 0, 0, &pEnts, &match);
		Util::FreeNativeString(charType);

		Entity^ result = nullptr;
		if (pEnts && (match > 0))
		{
			UdbEntity ent = pEnts[match - 1];
			result = Entity::Create(ent);
		}

		udbListEntityFree(pEnts);
		pEnts = 0;

		return result;
	}
	String^ Entity::ToString()
	{
		StringBuilder^ sb = gcnew StringBuilder();
		sb->AppendFormat("{0} - {1}", mNameSimple, mThisKind->NameLong);
		return sb->ToString();
	}
	Reference^ Entity::GetRef()
	{
		// mReference cannot be initialized in the constructor because 
		// a stack overflow will occur. That is because when an entity
		// is created, it creates a reference, which creates other Entity's, 
		// which creates other references, which creates other Entities. Can you
		// see where this is going?
		if (mReference == nullptr)
		{
			UdbReference* refs = nullptr;
			int numRefs = 0;
			UdbEntity entity = GetUdbEntity();
			udbListReference(entity, &refs, &numRefs);
			if ( (numRefs > 0) && (refs != nullptr) )
			{
				// Big assumption here!! Just a guess really!
				UdbReference aRef = refs[0];
				Debug::Assert(aRef != nullptr);
				mReference = gcnew Reference(aRef);
				Debug::Assert(mReference != nullptr);
				udbListReferenceFree(refs);
				refs = nullptr;
			}
		}
		return mReference;
	}
	UdbEntity Entity::GetUdbEntity()
	{
		char* tempStringUnique = Util::StringToChar(mUniqueName);
		UdbEntity ent = udbLookupEntityByUniquename(tempStringUnique);
		Util::FreeNativeString(tempStringUnique);
		Debug::Assert(ent != nullptr);
		return ent;
	}
	array<Reference^>^ Entity::Refs(String^ refKinds, String^ entKinds)
	{
		char* tempRefKind = Util::StringToChar(refKinds);
		char* tempEntKind = Util::StringToChar(entKinds);

		UdbEntity thisEntity = GetUdbEntity();
		if (thisEntity == nullptr)
			return nullptr;

#ifdef CACHING_POINTERS_BUG
		Debug::Assert(thisEntity == mEntity, "Entity pointers are not the same, you CANNOT cache UdbEntity Pointers");
#endif

		UdbReference* refs = nullptr;
		int unique = 1;
#ifdef CACHING_POINTERS_BUG
		int numRefs = udbEntityRefs(mEntity, tempRefKind, tempEntKind, unique, &refs );
#else
		int numRefs = udbEntityRefs(thisEntity, tempRefKind, tempEntKind, unique, &refs );
#endif
		Util::FreeNativeString(tempRefKind);
		tempRefKind = 0;
		Util::FreeNativeString(tempEntKind);
		tempEntKind = 0;

		List<Reference^>^ result = gcnew List<Reference^>();
		if ( (numRefs > 0) && (refs != nullptr) )
		{
			for (int i = 0; i < numRefs; i++)
			{
				UdbReference ref = refs[i];
				Debug::Assert(ref != nullptr);
				Reference^ refer = gcnew Reference(ref);
				Debug::Assert(refer != nullptr);
				result->Add(refer);
			}
		}
		return result->ToArray();
	}

	void Entity::InitializeBoundsaries(const UdbEntity entity)
	{
		UdbReference* refs = nullptr;
		int           refsSize = 0;
		udbListReference(entity, &refs, &refsSize);

		if ((refsSize > 0) && (refs != 0))
		{
			bool foundEnd = false;
			bool foundDefine = false;
			bool foundDeclare = false;
			for (int j = 0; j < refsSize; j++)
			{
				UdbReference ref = refs[j];
				::UdbKind refKind = udbReferenceKind(ref);
				char* kindName = udbKindShortname(refKind);
				String^ mankindName = gcnew String(kindName);
				//int lineNumber = udbReferenceLine(ref);
				//Debug::Assert(lineNumber >= 0);
				// a function declaration can have it's first reference be a declaration function.
				// be vigilant to only pick up define references
				if (mankindName->Contains("Define"))
				{
					if ((foundDefine == false))
					{
						mLineDefineStart = udbReferenceLine(ref);
						foundDefine = true;
					}
				}

				if (mankindName->Contains("Declare"))
				{
					if (foundDeclare == false)
					{
						mLineDeclareStart = udbReferenceLine(ref);
						foundDeclare = true;
					}
				}
				else if (mankindName->Contains("End"))
				{
					if (foundEnd == false)
					{
						mLineDefineEnd = udbReferenceLine(ref);
						foundEnd = true;
					}
				}
			}
			udbListReferenceFree(refs);
			refs = nullptr;
		}
	}
}